package net.danielthompson.danray.runners;

import net.danielthompson.danray.TraceManager;
import net.danielthompson.danray.Tracer;
import net.danielthompson.danray.shading.Blender;
import net.danielthompson.danray.structures.Scene;
import net.danielthompson.danray.structures.Vector;

import java.awt.*;

/**
 * Created by daniel on 3/4/14.
 */


public class PixelRunner implements Runnable {


   private final Object _lock = new Object();

   volatile int _xPointer;
   volatile int _yPointer;

   private final int _x;
   private final int _y;

   private final TraceManager _manager;
   private final Tracer _tracer;
   private final Scene _scene;

   private final int _samples;
   private final int _superSamples;

   public PixelRunner(TraceManager manager, Tracer tracer, Scene scene, int x, int y, int samples, int superSamples) {
      _manager = manager;
      _tracer = tracer;
      _scene = scene;
      _x = x;
      _y = y;
      _samples = samples;
      _superSamples = superSamples;

   }

   @Override
   public void run() {
      int[] pixel = GetNextPixel();
      while (pixel != null) {
         trace(pixel);
         if (pixel[0] == _x - 1)
            _manager.UpdateCanvas();
         pixel = GetNextPixel();
      }
   }

   /**
    * Traces and outputs the given pixel. Must not be null.
    * @param pixel
    */
   public void trace(int[] pixel) {

      int reachedSamples = 0;

      Vector[] cameraRays = _scene.Camera.GetInitialStochasticRaysForPixel(pixel[0], pixel[1], _samples);
      Color[] colors = new Color[cameraRays.length];
      for (int i = 0; i < cameraRays.length; i++) {
         colors[i] = _tracer.GetColorForRay(cameraRays[i], 1);
      }

      Color blendSoFar = Blender.BlendColors(colors);
      Color marginalBlend = blendSoFar;

      for (int j = 0; j < _superSamples; j++) {
         reachedSamples += _samples;
         cameraRays = _scene.Camera.GetInitialStochasticRaysForPixel(pixel[0], pixel[1], _samples);
         colors = new Color[cameraRays.length];
         for (int i = 0; i < cameraRays.length; i++) {
            colors[i] = _tracer.GetColorForRay(cameraRays[i], 1);
         }

         Color marginalColor = Blender.BlendColors(colors);
         
         marginalBlend = Blender.BlendWeighted(marginalColor, 1, blendSoFar, 1 + j);

         if (Blender.CloseEnough(blendSoFar, marginalBlend, .99f)) {
            blendSoFar = marginalBlend;
            break;
         }
         else {
            blendSoFar = marginalBlend;
         }

      }

      _manager.SetRayCountForPixel(pixel, reachedSamples);
      _manager.SetPixelColor(pixel, blendSoFar);

   }

   public int[] GetNextPixel() {

      int[] pixel = new int[2];
      pixel[0] = -1;
      pixel[1] = -1;

      synchronized (_lock) {

         if (_xPointer < _x && _yPointer < _y) {

            pixel[0] = _xPointer;
            pixel[1] = _yPointer;

            _xPointer++;

            if (_xPointer >= _x) {
               _xPointer = 0;
               _yPointer++;
            }
         }
      }

      if (pixel[0] == -1)
         return null;
      return pixel;
   }

}